/**
 * Copyright (c) 2013 Nokia Corporation. All rights reserved.
 * Nokia and Nokia Connecting People are registered trademarks of Nokia Corporation. 
 * Oracle and Java are trademarks or registered trademarks of Oracle and/or its
 * affiliates. Other product and company names mentioned herein may be trademarks
 * or trade names of their respective owners. 
 * See LICENSE.TXT for license information.
 */

package com.nokia.example.composedui.utils;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import javax.microedition.midlet.MIDlet;

/**
 * A utility class for storing key value pairs into an application specific
 * record store.
 */
public class AppDataStorage {
    // Constants
    private static final char KEY_VALUE_DELIMITER = '|';

    // Members
    private static AppDataStorage instance = null;
    private final String recordStoreName;
    private Hashtable data = null;
    private RecordStoreHelper rmsHelper = null;

    /** 
     * @param midlet The MIDlet instance of the application. A valid MIDlet
     *                  instance is required the first time this getter is called. 
     * @return The AppDataStorage instance.
     */
    public static AppDataStorage getInstance(MIDlet midlet) {
        if (instance == null) {
            instance = new AppDataStorage(midlet);
        }
        
        return instance;
    }

    /**
     * Constructor.
     * @param midlet The MIDlet instance of the application.
     */
    private AppDataStorage(MIDlet midlet) {
        if (midlet == null) {
            throw new IllegalArgumentException();
        }
        
        recordStoreName = generateRecordStoreName(midlet);
        rmsHelper = new RecordStoreHelper();
        data = new Hashtable();
    }

    /**
     * Inserts the given key value pair into the hash table. If a matching key
     * already exists in the table, the previous value will be overwritten.
     * @param key The key of the key value pair.
     * @param value The value of the key value pair.
     * @throws IllegalArgumentException If the either the key or the value
     * contains KEY_VALUE_DELIMITER character.
     */
    public void put(final String key, final String value) {
        
        if (key.indexOf(KEY_VALUE_DELIMITER) != -1
                || value.indexOf(KEY_VALUE_DELIMITER) != -1)
        {
            throw new IllegalArgumentException(
                    "Neither the key or the value are allowed to contain the delimiter character ("
                    + KEY_VALUE_DELIMITER + ")!");
        }
        
        data.put(key, value);
    }

    /**
     * Appends the given key value pair into the hash table. If a matching key
     * already exists in the table, this method will not overwrite the value.
     * @param key The key of the key value pair.
     * @param value The value of the key value pair.
     * @return True if the pair was appended, false if the key already exists.
     */
    public boolean append(final String key, final String value) {
        if (data.containsKey(key)) {
            return false;
        }
        
        put(key, value);
        return true;
    }

    /**
     * Returns a value in the hash table based on the given key.
     * @param key The key of the key value pair.
     * @return The value of the key value pair.
     */
    public String valueByKey(final String key) {
        String value = (String)data.get(key);
        System.out.println("AppDataStorage::valueByKey(): " + key + " => " + value);
        return value;
    }

    /**
     * Loads and parses the record store content.
     * @see AppDataStorage#doLoad
     * @return True if successful, false otherwise.
     */
    public boolean load() {
        return doLoad(false);
    }

    /**
     * Saves the hash table content into the record store.
     * @param overwrite If true, will purge the old records before saving the
     * data. If false, will append new data and replace the existing data if the
     * keys match.
     * @return True if successful, false otherwise.
     */
    public boolean save(boolean overwrite) {
        if (overwrite) {
            deletePermanently();
            
            if (!rmsHelper.open(recordStoreName)) {
                System.out.println("AppDataStorage::doLoad(): Failed to open the RMS!");
                return false;
            }
        }
        else {
            if (!doLoad(true)) {
                // Failed to load the content
                return false;
            }
        }
        
        Enumeration keys = data.keys();
        String key = null;
        String record = null;
        boolean result = true;
        
        while (keys.hasMoreElements()) {
            key = (String)keys.nextElement();
            record = key + KEY_VALUE_DELIMITER + data.get(key).toString();
            result = rmsHelper.addRecord(record.getBytes()) && result;
            System.out.println("AppDataStorage::save(): Saved: " + key + ": " + data.get(key).toString());
        }
        
        rmsHelper.close();
        return result;
    }

    /**
     * Clears the local copy of the data. It is recommended to clear the local
     * data if there's a lot of data occupying memory and there's no need to
     * access it frequently.
     */
    public void clear() {
        data.clear();
    }

    /**
     * Deletes all the data in the record store.
     * @return True if successful, false otherwise.
     */
    public boolean deletePermanently() {
        return rmsHelper.clear(recordStoreName);
    }

    /**
     * Loads and parses the content of the record store. Populates the internal
     * hash table with the parsed content.
     * @param keepRmsOpen If true, will not close the record store after done
     *                       reading.
     * @return True if successful, false otherwise.
     */
    private boolean doLoad(boolean keepRmsOpen) {
        if (!rmsHelper.open(recordStoreName)) {
            System.out.println("AppDataStorage::doLoad(): Failed to open the RMS!");
            return false;
        }
        
        Vector records = rmsHelper.getRecords();
        final int size = records.size();
        System.out.println("AppDataStorage::doLoad(): Record count is " + size + ".");
        String element = null;
        int delimiterIndex = -1;
        String key = null;
        String value = null;
        
        for (int i = 0; i < size; ++i) {
            element = (String)records.elementAt(i);
            delimiterIndex = element.indexOf(KEY_VALUE_DELIMITER);
            key = element.substring(0, delimiterIndex);
            value = element.substring(delimiterIndex + 1, element.length());
            System.out.println("AppDataStorage::doLoad(): Loaded: " + key + ": " + value);
            
            if (!data.containsKey(key)) {
                data.put(key, value);
            }
        }
        
        if (!keepRmsOpen) {
            rmsHelper.close();
        }
        
        return true;
    }

    /**
     * Creates a new string identical to the given one except all the white
     * spaces removed.
     * @param str The original string.
     * @return A new string with the same content as the given one but without
     *            white spaces.
     */
    private String removeWhiteSpaces(String str) {
        StringBuffer buffer = new StringBuffer();
        final int length = str.length();
        char ch;
        
        for (int i = 0; i < length; ++i) {
            ch = str.charAt(i);
            
            if (ch != ' ') {
                buffer.append(str.charAt(i));
            }
        }
        
        return buffer.toString();
    }

    /**
     * Generates the name for the record store based on the MIDlet name and the
     * MIDlet vendor.
     * @param midlet The MIDlet of the application.
     * @return The generated name as a string.
     */
    private String generateRecordStoreName(MIDlet midlet) {
        String name = removeWhiteSpaces(midlet.getAppProperty("MIDlet-Name")
                + midlet.getAppProperty("MIDlet-Vendor"));
        
        // The maximum allowed length of a record store name is 32 characters
        if (name.length() > 32) {
            return name.substring(0, 31);
        }
        
        return name;
    }
}
